/*
    Copyright © 2010  Ahti Legonkov
    
    This file is part of j+-.
    
    j+- is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.
    
    j+- is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.
    
    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

package cx.ath.skyflyer.javapm.visitor;

import japa.parser.ast.BlockComment;
import japa.parser.ast.CompilationUnit;
import japa.parser.ast.ImportDeclaration;
import japa.parser.ast.LineComment;
import japa.parser.ast.Node;
import japa.parser.ast.PackageDeclaration;
import japa.parser.ast.TypeParameter;
import japa.parser.ast.body.AnnotationDeclaration;
import japa.parser.ast.body.AnnotationMemberDeclaration;
import japa.parser.ast.body.BodyDeclaration;
import japa.parser.ast.body.ClassOrInterfaceDeclaration;
import japa.parser.ast.body.ConstructorDeclaration;
import japa.parser.ast.body.DestructorDeclaration;
import japa.parser.ast.body.EmptyMemberDeclaration;
import japa.parser.ast.body.EmptyTypeDeclaration;
import japa.parser.ast.body.EnumConstantDeclaration;
import japa.parser.ast.body.EnumDeclaration;
import japa.parser.ast.body.FieldDeclaration;
import japa.parser.ast.body.InitializerDeclaration;
import japa.parser.ast.body.JavadocComment;
import japa.parser.ast.body.MethodDeclaration;
import japa.parser.ast.body.Parameter;
import japa.parser.ast.body.TypeDeclaration;
import japa.parser.ast.body.VariableDeclarator;
import japa.parser.ast.body.VariableDeclaratorId;
import japa.parser.ast.expr.AnnotationExpr;
import japa.parser.ast.expr.ArrayAccessExpr;
import japa.parser.ast.expr.ArrayCreationExpr;
import japa.parser.ast.expr.ArrayInitializerExpr;
import japa.parser.ast.expr.AssignExpr;
import japa.parser.ast.expr.BinaryExpr;
import japa.parser.ast.expr.BooleanLiteralExpr;
import japa.parser.ast.expr.CastExpr;
import japa.parser.ast.expr.CharLiteralExpr;
import japa.parser.ast.expr.ClassExpr;
import japa.parser.ast.expr.ConditionalExpr;
import japa.parser.ast.expr.DoubleLiteralExpr;
import japa.parser.ast.expr.EnclosedExpr;
import japa.parser.ast.expr.Expression;
import japa.parser.ast.expr.FieldAccessExpr;
import japa.parser.ast.expr.InstanceOfExpr;
import japa.parser.ast.expr.IntegerLiteralExpr;
import japa.parser.ast.expr.IntegerLiteralMinValueExpr;
import japa.parser.ast.expr.LongLiteralExpr;
import japa.parser.ast.expr.LongLiteralMinValueExpr;
import japa.parser.ast.expr.MarkerAnnotationExpr;
import japa.parser.ast.expr.MemberValuePair;
import japa.parser.ast.expr.MethodCallExpr;
import japa.parser.ast.expr.NameExpr;
import japa.parser.ast.expr.NormalAnnotationExpr;
import japa.parser.ast.expr.NullLiteralExpr;
import japa.parser.ast.expr.ObjectCreationExpr;
import japa.parser.ast.expr.QualifiedNameExpr;
import japa.parser.ast.expr.SingleMemberAnnotationExpr;
import japa.parser.ast.expr.StringLiteralExpr;
import japa.parser.ast.expr.SuperExpr;
import japa.parser.ast.expr.ThisExpr;
import japa.parser.ast.expr.UnaryExpr;
import japa.parser.ast.expr.VariableDeclarationExpr;
import japa.parser.ast.stmt.AssertStmt;
import japa.parser.ast.stmt.BlockStmt;
import japa.parser.ast.stmt.BreakStmt;
import japa.parser.ast.stmt.CatchClause;
import japa.parser.ast.stmt.ContinueStmt;
import japa.parser.ast.stmt.DoStmt;
import japa.parser.ast.stmt.EmptyStmt;
import japa.parser.ast.stmt.ExplicitConstructorInvocationStmt;
import japa.parser.ast.stmt.ExpressionStmt;
import japa.parser.ast.stmt.ForStmt;
import japa.parser.ast.stmt.ForeachStmt;
import japa.parser.ast.stmt.IfStmt;
import japa.parser.ast.stmt.LabeledStmt;
import japa.parser.ast.stmt.ReturnStmt;
import japa.parser.ast.stmt.Statement;
import japa.parser.ast.stmt.SwitchEntryStmt;
import japa.parser.ast.stmt.SwitchStmt;
import japa.parser.ast.stmt.SynchronizedStmt;
import japa.parser.ast.stmt.ThrowStmt;
import japa.parser.ast.stmt.TryStmt;
import japa.parser.ast.stmt.TypeDeclarationStmt;
import japa.parser.ast.stmt.WhileStmt;
import japa.parser.ast.type.ClassOrInterfaceType;
import japa.parser.ast.type.PrimitiveType;
import japa.parser.ast.type.ReferenceType;
import japa.parser.ast.type.Type;
import japa.parser.ast.type.VoidType;
import japa.parser.ast.type.WildcardType;
import japa.parser.ast.visitor.GenericVisitor;
import java.util.ArrayList;
import java.util.List;

/**
 * @author Ahti Legonkov
 */
public class CloneVisitor implements GenericVisitor<Node, Object>
{
    public Node visit(CompilationUnit n, Object arg) {
        CompilationUnit res = new CompilationUnit();
        if (n.getPackage() != null) {
            PackageDeclaration pd = (PackageDeclaration)n.getPackage().accept(this, arg);
            res.setPackage(pd);
        }
        if (n.getImports() != null) {
            List<ImportDeclaration> imports = new ArrayList<ImportDeclaration>();
            res.setImports(imports);
            for (ImportDeclaration i : n.getImports()) {
                ImportDeclaration id = (ImportDeclaration)i.accept(this, arg);
                imports.add(id);
            }
        }
        if (n.getTypes() != null) {
            List<TypeDeclaration> types = new ArrayList<TypeDeclaration>();
            res.setTypes(types);
            for (TypeDeclaration typeDeclaration : n.getTypes()) {
                TypeDeclaration td = (TypeDeclaration)typeDeclaration.accept(this, arg);
                types.add(td);
            }
        }
        return res;
    }

    private <T extends Node> List<T> cloneListOfT(List<T> src, Object arg)
    {
        if (src == null)
            return null;

        List<T> res = new ArrayList<T>();
        for (T t : src) {
            T tp = (T)t.accept(this, arg);
            res.add(tp);
        }

        return res;
    }

    public Node visit(PackageDeclaration n, Object arg) {
        PackageDeclaration res = new PackageDeclaration();
        List<AnnotationExpr> annotations = cloneListOfT(n.getAnnotations(), arg);
        res.setAnnotations(annotations);

        NameExpr ne = (NameExpr)n.getName().accept(this, arg);
        res.setName(ne);
        
        return res;
    }

    public Node visit(ImportDeclaration n, Object arg) {
        ImportDeclaration res = new ImportDeclaration();
        NameExpr ne = (NameExpr)n.getName().accept(this, arg);
        res.setName(ne);
        return res;
    }

    public Node visit(TypeParameter n, Object arg) {
        TypeParameter res = new TypeParameter();
        if (n.getTypeBound() != null) {
            List<ClassOrInterfaceType> typebound = new ArrayList<ClassOrInterfaceType>();
            res.setTypeBound(typebound);
            for (ClassOrInterfaceType c : n.getTypeBound()) {
                ClassOrInterfaceType ct = (ClassOrInterfaceType)c.accept(this, arg);
                typebound.add(ct);
            }
        }
        return res;
    }

    public Node visit(LineComment n, Object arg) {
        LineComment res = new LineComment();
        res.setContent(n.getContent());
        return res;
    }

    public Node visit(BlockComment n, Object arg) {
        BlockComment res = new BlockComment();
        res.setContent(n.getContent());
        return res;
    }

    public Node visit(ClassOrInterfaceDeclaration n, Object arg) {
        ClassOrInterfaceDeclaration res = new ClassOrInterfaceDeclaration();
        if (n.getJavaDoc() != null) {
            JavadocComment jdc = (JavadocComment)n.getJavaDoc().accept(this, arg);
            res.setJavaDoc(jdc);
        }
        List<AnnotationExpr> annotations = cloneListOfT(n.getAnnotations(), arg);
        res.setAnnotations(annotations);
        
        List<TypeParameter> typeParameters = cloneListOfT(n.getTypeParameters(), arg);
        res.setTypeParameters(typeParameters);

        List<ClassOrInterfaceType> exts = cloneListOfT(n.getExtends(), arg);
        res.setExtends(exts);

        List<ClassOrInterfaceType> impls = cloneListOfT(n.getImplements(), arg);
        res.setImplements(impls);

        List<BodyDeclaration> members = cloneListOfT(n.getMembers(), arg);
        res.setMembers(members);

        return res;
    }

    private JavadocComment cloneJavadoc(JavadocComment jdc, Object arg)
    {
        if (jdc == null)
            return null;

        return (JavadocComment)jdc.accept(this, arg);
    }

    public Node visit(EnumDeclaration n, Object arg) {
        EnumDeclaration res = new EnumDeclaration();

        JavadocComment jdc = cloneJavadoc(n.getJavaDoc(), arg);
        res.setJavaDoc(jdc);

        List<AnnotationExpr> annotations = cloneListOfT(n.getAnnotations(), arg);
        res.setAnnotations(annotations);

        List<ClassOrInterfaceType> impls = cloneListOfT(n.getImplements(), arg);
        res.setImplements(impls);

        List<EnumConstantDeclaration> entries = cloneListOfT(n.getEntries(), arg);
        res.setEntries(entries);

        List<BodyDeclaration> members = cloneListOfT(n.getMembers(), arg);
        res.setMembers(members);

        return res;
    }

    public Node visit(EmptyTypeDeclaration n, Object arg) {
        EmptyTypeDeclaration res = new EmptyTypeDeclaration();

        JavadocComment jdc = cloneJavadoc(n.getJavaDoc(), arg);
        res.setJavaDoc(jdc);

        return res;
    }

    public Node visit(EnumConstantDeclaration n, Object arg) {
        EnumConstantDeclaration res = new EnumConstantDeclaration();

        JavadocComment jdc = cloneJavadoc(n.getJavaDoc(), arg);
        res.setJavaDoc(jdc);

        List<AnnotationExpr> annotations = cloneListOfT(n.getAnnotations(), arg);
        res.setAnnotations(annotations);

        List<Expression> args = cloneListOfT(n.getArgs(), arg);
        res.setArgs(args);

        List<BodyDeclaration> members = cloneListOfT(n.getClassBody(), arg);
        res.setClassBody(members);

        return res;
    }

    public Node visit(AnnotationDeclaration n, Object arg) {
        AnnotationDeclaration res = new AnnotationDeclaration();

        JavadocComment jdc = cloneJavadoc(n.getJavaDoc(), arg);
        res.setJavaDoc(jdc);

        List<AnnotationExpr> annotations = cloneListOfT(n.getAnnotations(), arg);
        res.setAnnotations(annotations);

        List<BodyDeclaration> members = cloneListOfT(n.getMembers(), arg);
        res.setMembers(members);

        return res;
    }

    public Node visit(AnnotationMemberDeclaration n, Object arg) {
        AnnotationMemberDeclaration res = new AnnotationMemberDeclaration();

        JavadocComment jdc = cloneJavadoc(n.getJavaDoc(), arg);
        res.setJavaDoc(jdc);

        List<AnnotationExpr> annotations = cloneListOfT(n.getAnnotations(), arg);
        res.setAnnotations(annotations);

        Type type = (Type)n.getType().accept(this, arg);
        res.setType(type);

        if (n.getDefaultValue() != null) {
            Expression e = (Expression)n.getDefaultValue().accept(this, arg);
            res.setDefaultValue(e);
        }

        return res;
    }

    public Node visit(FieldDeclaration n, Object arg) {
        FieldDeclaration res = new FieldDeclaration();

        JavadocComment jdc = cloneJavadoc(n.getJavaDoc(), arg);
        res.setJavaDoc(jdc);

        List<AnnotationExpr> annotations = cloneListOfT(n.getAnnotations(), arg);
        res.setAnnotations(annotations);

        Type type = (Type)n.getType().accept(this, arg);
        res.setType(type);

        List<VariableDeclarator> vars = cloneListOfT(n.getVariables(), arg);
        res.setVariables(vars);

        return res;
    }

    public Node visit(VariableDeclarator n, Object arg) {
        VariableDeclarator res = new VariableDeclarator();

        VariableDeclaratorId id = (VariableDeclaratorId)n.getId().accept(this, arg);
        res.setId(id);
        
        if (n.getInit() != null) {
            Expression e = (Expression)n.getInit().accept(this, arg);
            res.setInit(e);
        }
        
        return res;
    }

    public Node visit(VariableDeclaratorId n, Object arg) {
        VariableDeclaratorId res = new VariableDeclaratorId();

        res.setName(n.getName());

        return res;
    }

    public Node visit(ConstructorDeclaration n, Object arg) {
        ConstructorDeclaration res = new ConstructorDeclaration();

        JavadocComment jdc = cloneJavadoc(n.getJavaDoc(), arg);
        res.setJavaDoc(jdc);

        List<AnnotationExpr> annotations = cloneListOfT(n.getAnnotations(), arg);
        res.setAnnotations(annotations);

        List<TypeParameter> typeParameters = cloneListOfT(n.getTypeParameters(), arg);
        res.setTypeParameters(typeParameters);

        List<Parameter> parameters = cloneListOfT(n.getParameters(), arg);
        res.setParameters(parameters);

        List<NameExpr> throws_ = cloneListOfT(n.getThrows(), arg);
        res.setThrows(throws_);

        BlockStmt block = (BlockStmt)n.getBlock().accept(this, arg);
        res.setBlock(block);
        
        return res;
    }

    public Node visit(DestructorDeclaration n, Object arg) {
        DestructorDeclaration res = new DestructorDeclaration();

        JavadocComment jdc = cloneJavadoc(n.getJavaDoc(), arg);
        res.setJavaDoc(jdc);

        List<AnnotationExpr> annotations = cloneListOfT(n.getAnnotations(), arg);
        res.setAnnotations(annotations);

        List<NameExpr> throws_ = cloneListOfT(n.getThrows(), arg);
        res.setThrows(throws_);

        BlockStmt block = (BlockStmt)n.getBlock().accept(this, arg);
        res.setBlock(block);

        return res;
    }

    public Node visit(MethodDeclaration n, Object arg) {
        MethodDeclaration res = new MethodDeclaration();

        JavadocComment jdc = cloneJavadoc(n.getJavaDoc(), arg);
        res.setJavaDoc(jdc);

        List<AnnotationExpr> annotations = cloneListOfT(n.getAnnotations(), arg);
        res.setAnnotations(annotations);

        List<TypeParameter> typeParameters = cloneListOfT(n.getTypeParameters(), arg);
        res.setTypeParameters(typeParameters);

        Type type = (Type)n.getType().accept(this, arg);
        res.setType(type);

        List<Parameter> parameters = cloneListOfT(n.getParameters(), arg);
        res.setParameters(parameters);

        List<NameExpr> throws_ = cloneListOfT(n.getThrows(), arg);
        res.setThrows(throws_);

        if (n.getBody() != null) {
            BlockStmt block = (BlockStmt)n.getBody().accept(this, arg);
            res.setBody(block);
        }

        return res;
    }

    public Node visit(Parameter n, Object arg) {
        Parameter res = new Parameter();

        List<AnnotationExpr> annotations = cloneListOfT(n.getAnnotations(), arg);
        res.setAnnotations(annotations);

        Type type = (Type)n.getType().accept(this, arg);
        res.setType(type);

        VariableDeclaratorId id = (VariableDeclaratorId)n.getId().accept(this, arg);
        res.setId(id);
        
        return res;
    }

    public Node visit(EmptyMemberDeclaration n, Object arg) {
        EmptyMemberDeclaration res = new EmptyMemberDeclaration();

        JavadocComment jdc = cloneJavadoc(n.getJavaDoc(), arg);
        res.setJavaDoc(jdc);

        return res;
    }

    public Node visit(InitializerDeclaration n, Object arg) {
        InitializerDeclaration res = new InitializerDeclaration();

        JavadocComment jdc = cloneJavadoc(n.getJavaDoc(), arg);
        res.setJavaDoc(jdc);

        res.setBlock((BlockStmt)n.getBlock().accept(this, arg));

        return res;
    }

    public Node visit(JavadocComment n, Object arg) {
        JavadocComment res = new JavadocComment();

        res.setContent(n.getContent());
        
        return res;
    }

    public Node visit(ClassOrInterfaceType n, Object arg) {
        ClassOrInterfaceType res = new ClassOrInterfaceType();

        if (n.getScope() != null) {
            ClassOrInterfaceType scope = (ClassOrInterfaceType)n.getScope().accept(this, arg);
            res.setScope(scope);
        }

        List<Type> typeArgs = cloneListOfT(n.getTypeArgs(), arg);
        res.setTypeArgs(typeArgs);

        return res;
    }

    public Node visit(PrimitiveType n, Object arg) {
        PrimitiveType res = new PrimitiveType();

        res.setType(n.getType());
        
        return res;
    }

    public Node visit(ReferenceType n, Object arg) {
        ReferenceType res = new ReferenceType();

        Type type = (Type)n.getType().accept(this, arg);
        res.setType(type);
        
        return res;
    }

    public Node visit(VoidType n, Object arg) {
        VoidType res = new VoidType();

        return res;
    }

    public Node visit(WildcardType n, Object arg) {
        WildcardType res = new WildcardType();

        if (n.getExtends() != null) {
            ReferenceType rt = (ReferenceType)n.getExtends().accept(this, arg);
            res.setExtends(rt);
        }

        if (n.getSuper() != null) {
            ReferenceType rt = (ReferenceType)n.getSuper().accept(this, arg);
            res.setSuper(rt);
        }
        
        return res;
    }

    public Node visit(ArrayAccessExpr n, Object arg) {
        ArrayAccessExpr res = new ArrayAccessExpr();
        
        Expression name = (Expression)n.getName().accept(this, arg);
        res.setName(name);

        Expression index = (Expression)n.getIndex().accept(this, arg);
        res.setIndex(index);
        
        return res;
    }

    public Node visit(ArrayCreationExpr n, Object arg) {
        ArrayCreationExpr res = new ArrayCreationExpr();

        Type type = (Type)n.getType().accept(this, arg);
        res.setType(type);

        if (n.getDimensions() != null) {
            List<Expression> dims = cloneListOfT(n.getDimensions(), arg);
            res.setDimensions(dims);
        } else {
            ArrayInitializerExpr init = (ArrayInitializerExpr)n.getInitializer().accept(this, arg);
            res.setInitializer(init);
        }

        return res;
    }

    public Node visit(ArrayInitializerExpr n, Object arg) {
        ArrayInitializerExpr res = new ArrayInitializerExpr();

        List<Expression> values = cloneListOfT(n.getValues(), arg);
        res.setValues(values);

        return res;
    }

    public Node visit(AssignExpr n, Object arg) {
        AssignExpr res = new AssignExpr();
        res.setTarget((Expression)n.getTarget().accept(this, arg));
        res.setValue((Expression)n.getValue().accept(this, arg));
        return res;
    }

    public Node visit(BinaryExpr n, Object arg) {
        BinaryExpr res = new BinaryExpr();
        res.setLeft((Expression)n.getLeft().accept(this, arg));
        res.setRight((Expression)n.getRight().accept(this, arg));
        return res;
    }

    public Node visit(CastExpr n, Object arg) {
        CastExpr res = new CastExpr();
        res.setType((Type)n.getType().accept(this, arg));
        res.setExpr((Expression)n.getExpr().accept(this, arg));
        return res;
    }

    public Node visit(ClassExpr n, Object arg) {
        ClassExpr res = new ClassExpr();
        res.setType((Type)n.getType().accept(this, arg));
        return res;
    }

    public Node visit(ConditionalExpr n, Object arg) {
        ConditionalExpr res = new ConditionalExpr();
        res.setCondition((Expression)n.getCondition().accept(this, arg));
        res.setThenExpr((Expression)n.getThenExpr().accept(this, arg));
        res.setElseExpr((Expression)n.getElseExpr().accept(this, arg));
        return res;
    }

    public Node visit(EnclosedExpr n, Object arg) {
        EnclosedExpr res = new EnclosedExpr();
        res.setInner((Expression)n.getInner().accept(this, arg));
        return res;
    }

    public Node visit(FieldAccessExpr n, Object arg) {
        FieldAccessExpr res = new FieldAccessExpr();
        res.setScope((Expression)n.getScope().accept(this, arg));
        return res;
    }

    public Node visit(InstanceOfExpr n, Object arg) {
        InstanceOfExpr res = new InstanceOfExpr();
        res.setExpr((Expression)n.getExpr().accept(this, arg));
        res.setType((Type)n.getType().accept(this, arg));
        return res;
    }

    public Node visit(StringLiteralExpr n, Object arg) {
        StringLiteralExpr res = new StringLiteralExpr();
        res.setValue(n.getValue());
        return res;
    }

    public Node visit(IntegerLiteralExpr n, Object arg) {
        IntegerLiteralExpr res = new IntegerLiteralExpr();
        res.setValue(n.getValue());
        return res;
    }

    public Node visit(LongLiteralExpr n, Object arg) {
        LongLiteralExpr res = new LongLiteralExpr();
        res.setValue(n.getValue());
        return res;
    }

    public Node visit(IntegerLiteralMinValueExpr n, Object arg) {
        IntegerLiteralMinValueExpr res = new IntegerLiteralMinValueExpr();
        res.setValue(n.getValue());
        return res;
    }

    public Node visit(LongLiteralMinValueExpr n, Object arg) {
        LongLiteralMinValueExpr res = new LongLiteralMinValueExpr();
        res.setValue(n.getValue());
        return res;
    }

    public Node visit(CharLiteralExpr n, Object arg) {
        CharLiteralExpr res = new CharLiteralExpr();
        res.setValue(n.getValue());
        return res;
    }

    public Node visit(DoubleLiteralExpr n, Object arg) {
        DoubleLiteralExpr res = new DoubleLiteralExpr();
        res.setValue(n.getValue());
        return res;
    }

    public Node visit(BooleanLiteralExpr n, Object arg) {
        BooleanLiteralExpr res = new BooleanLiteralExpr();
        res.setValue(n.getValue());
        return res;
    }

    public Node visit(NullLiteralExpr n, Object arg) {
        NullLiteralExpr res = new NullLiteralExpr();
        return res;
    }

    public Node visit(MethodCallExpr n, Object arg) {
        MethodCallExpr res = new MethodCallExpr();

        if (n.getScope() != null) {
            Expression e = (Expression)n.getScope().accept(this, arg);
            res.setScope(e);
        }

        List<Type> typeArgs = cloneListOfT(n.getTypeArgs(), arg);
        res.setTypeArgs(typeArgs);

        List<Expression> args = cloneListOfT(n.getArgs(), arg);
        res.setArgs(args);

        return res;
    }

    public Node visit(NameExpr n, Object arg) {
        NameExpr res = new NameExpr();
        res.setName(n.getName());
        return res;
    }

    public Node visit(ObjectCreationExpr n, Object arg) {
        ObjectCreationExpr res = new ObjectCreationExpr();

        if (n.getScope() != null) {
            Expression e = (Expression)n.getScope().accept(this, arg);
            res.setScope(e);
        }

        List<Type> typeArgs = cloneListOfT(n.getTypeArgs(), arg);
        res.setTypeArgs(typeArgs);

        res.setType((ClassOrInterfaceType)n.getType().accept(this, arg));

        List<Expression> args = cloneListOfT(n.getArgs(), arg);
        res.setArgs(args);

        if (n.getAnonymousClassBody() != null) {
            List<BodyDeclaration> anonBody = cloneListOfT(n.getAnonymousClassBody(), arg);
            res.setAnonymousClassBody(anonBody);
        }
        
        return res;
    }

    public Node visit(QualifiedNameExpr n, Object arg) {
        QualifiedNameExpr res = new QualifiedNameExpr();
        n.setQualifier((NameExpr)n.getQualifier().accept(this, arg));
        return res;
    }

    public Node visit(ThisExpr n, Object arg) {
        ThisExpr res = new ThisExpr();
        if (n.getClassExpr() != null) {
            n.setClassExpr((Expression)n.getClassExpr().accept(this, arg));
        }
        return res;
    }

    public Node visit(SuperExpr n, Object arg) {
        SuperExpr res = new SuperExpr();
        if (n.getClassExpr() != null) {
            res.setClassExpr((Expression)n.getClassExpr().accept(this, arg));
        }
        return res;
    }

    public Node visit(UnaryExpr n, Object arg) {
        UnaryExpr res = new UnaryExpr();
        res.setExpr((Expression)n.getExpr().accept(this, arg));
        return res;
    }

    public Node visit(VariableDeclarationExpr n, Object arg) {
        VariableDeclarationExpr res = new VariableDeclarationExpr();

        List<AnnotationExpr> annotations = cloneListOfT(n.getAnnotations(), arg);
        res.setAnnotations(annotations);

        res.setType((Type)n.getType().accept(this, arg));

        List<VariableDeclarator> vars = cloneListOfT(n.getVars(), arg);
        res.setVars(vars);

        return res;
    }

    public Node visit(MarkerAnnotationExpr n, Object arg) {
        MarkerAnnotationExpr res = new MarkerAnnotationExpr();
        res.setName((NameExpr)n.getName().accept(this, arg));
        return res;
    }

    public Node visit(SingleMemberAnnotationExpr n, Object arg) {
        SingleMemberAnnotationExpr res = new SingleMemberAnnotationExpr();

        res.setName((NameExpr)n.getName().accept(this, arg));
        res.setMemberValue((Expression)n.getMemberValue().accept(this, arg));

        return res;
    }

    public Node visit(NormalAnnotationExpr n, Object arg) {
        NormalAnnotationExpr res = new NormalAnnotationExpr();

        res.setName((NameExpr)n.getName().accept(this, arg));
        if (n.getPairs() != null) {
            List<MemberValuePair> mp = new ArrayList<MemberValuePair>();
            res.setPairs(mp);
            for (MemberValuePair m : n.getPairs()) {
                mp.add((MemberValuePair)m.accept(this, arg));
            }
        }

        return res;
    }

    public Node visit(MemberValuePair n, Object arg) {
        MemberValuePair res = new MemberValuePair();
        res.setValue((Expression)n.getValue().accept(this, arg));
        return res;
    }

    public Node visit(ExplicitConstructorInvocationStmt n, Object arg) {
        ExplicitConstructorInvocationStmt res = new ExplicitConstructorInvocationStmt();

        res.setThis(n.isThis());
        if (!n.isThis() && n.getExpr() != null) {
            res.setExpr((Expression)n.getExpr().accept(this, arg));
        }
        if (n.getTypeArgs() != null) {
            List<Type> ta = new ArrayList<Type>();
            res.setTypeArgs(ta);
            for (Type t : n.getTypeArgs()) {
                ta.add((Type)t.accept(this, arg));
            }
        }
        if (n.getArgs() != null) {
            List<Expression> args = new ArrayList<Expression>();
            res.setArgs(args);
            for (Expression e : n.getArgs()) {
                args.add((Expression)e.accept(this, arg));
            }
        }

        return res;
    }

    public Node visit(TypeDeclarationStmt n, Object arg) {
        TypeDeclarationStmt res = new TypeDeclarationStmt();
        res.setTypeDeclaration((TypeDeclaration)n.getTypeDeclaration().accept(this, arg));
        return res;
    }

    public Node visit(AssertStmt n, Object arg) {
        AssertStmt res = new AssertStmt();

        res.setCheck((Expression)n.getCheck().accept(this, arg));

        if (n.getMessage() != null) {
            res.setMessage((Expression)n.getMessage().accept(this, arg));
        }

        return res;
    }

    public Node visit(BlockStmt n, Object arg) {
        BlockStmt res = new BlockStmt();

        List<Statement> stmts = cloneListOfT(n.getStmts(), arg);
        res.setStmts(stmts);

        return res;
    }

    public Node visit(LabeledStmt n, Object arg) {
        LabeledStmt res = new LabeledStmt();
        res.setStmt((Statement)n.getStmt().accept(this, arg));
        res.setLabel(n.getLabel());
        return res;
    }

    public Node visit(EmptyStmt n, Object arg) {
        EmptyStmt res = new EmptyStmt();
        return res;
    }

    public Node visit(ExpressionStmt n, Object arg) {
        ExpressionStmt res = new ExpressionStmt();
        res.setExpression((Expression)n.getExpression().accept(this, arg));
        return res;
    }

    public Node visit(SwitchStmt n, Object arg) {
        SwitchStmt res = new SwitchStmt();

        res.setSelector((Expression)n.getSelector().accept(this, arg));
        if (n.getEntries() != null) {
            List<SwitchEntryStmt> entries = cloneListOfT(n.getEntries(), arg);
            res.setEntries(entries);
        }
        
        return res;
    }

    public Node visit(SwitchEntryStmt n, Object arg) {
        SwitchEntryStmt res = new SwitchEntryStmt();

        if (n.getLabel() != null) {
            res.setLabel((Expression)n.getLabel().accept(this, arg));
        }
        if (n.getStmts() != null) {
            List<Statement> stmts = cloneListOfT(n.getStmts(), arg);
            res.setStmts(stmts);
        }

        return res;
    }

    public Node visit(BreakStmt n, Object arg) {
        BreakStmt res = new BreakStmt();
        res.setId(n.getId());
        return res;
    }

    public Node visit(ReturnStmt n, Object arg) {
        ReturnStmt res = new ReturnStmt();
        if (n.getExpr() != null) {
            res.setExpr((Expression)n.getExpr().accept(this, arg));
        }
        return res;
    }

    public Node visit(IfStmt n, Object arg) {
        IfStmt res = new IfStmt();
        res.setCondition((Expression)n.getCondition().accept(this, arg));
        res.setThenStmt((Statement)n.getThenStmt().accept(this, arg));
        if (n.getElseStmt() != null) {
            res.setElseStmt((Statement)n.getElseStmt().accept(this, arg));
        }
        return res;
    }

    public Node visit(WhileStmt n, Object arg) {
        WhileStmt res = new WhileStmt();
        res.setCondition((Expression)n.getCondition().accept(this, arg));
        res.setBody((Statement)n.getBody().accept(this, arg));
        return res;
    }

    public Node visit(ContinueStmt n, Object arg) {
        ContinueStmt res = new ContinueStmt();
        res.setId(n.getId());
        return res;
    }

    public Node visit(DoStmt n, Object arg) {
        DoStmt res = new DoStmt();
        res.setBody((Statement)n.getBody().accept(this, arg));
        res.setCondition((Expression)n.getCondition().accept(this, arg));
        return res;
    }

    public Node visit(ForeachStmt n, Object arg) {
        ForeachStmt res = new ForeachStmt();
        res.setVariable((VariableDeclarationExpr)n.getVariable().accept(this, arg));
        res.setIterable((Expression)n.getIterable().accept(this, arg));
        res.setBody((Statement)n.getBody().accept(this, arg));
        return res;
    }

    public Node visit(ForStmt n, Object arg) {
        ForStmt res = new ForStmt();

        if (n.getInit() != null) {
            List<Expression> init = cloneListOfT(n.getInit(), arg);
            res.setInit(init);
        }
        if (n.getCompare() != null) {
            Expression cmp = (Expression)n.getCompare().accept(this, arg);
            res.setCompare(cmp);
        }
        if (n.getUpdate() != null) {
            List<Expression> update = cloneListOfT(n.getUpdate(), arg);
            res.setUpdate(update);
        }
        res.setBody((Statement)n.getBody().accept(this, arg));

        return res;
    }

    public Node visit(ThrowStmt n, Object arg) {
        ThrowStmt res = new ThrowStmt();
        res.setExpr((Expression)n.getExpr().accept(this, arg));
        return res;
    }

    public Node visit(SynchronizedStmt n, Object arg) {
        SynchronizedStmt res = new SynchronizedStmt();
        res.setExpr((Expression)n.getExpr().accept(this, arg));
        res.setBlock((BlockStmt)n.getBlock().accept(this, arg));
        return res;
    }

    public Node visit(TryStmt n, Object arg) {
        TryStmt res = new TryStmt();
        
        res.setTryBlock((BlockStmt)n.getTryBlock().accept(this, arg));
        if (n.getCatchs() != null) {
            List<CatchClause> catches = cloneListOfT(n.getCatchs(), arg);
            res.setCatchs(catches);
        }
        
        if (n.getFinallyBlock() != null) {
            BlockStmt fin = (BlockStmt)n.getFinallyBlock().accept(this, arg);
            res.setFinallyBlock(fin);
        }

        return res;
    }

    public Node visit(CatchClause n, Object arg) {
        CatchClause res = new CatchClause();
        res.setExcept((Parameter)n.getExcept().accept(this, arg));
        res.setCatchBlock((BlockStmt)n.getCatchBlock().accept(this, arg));
        return res;
    }
}
